﻿using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Snow_CSharp
{
    // 包装一个分层绘制的窗口工具
    public class LayeredWindowTool
    {
        public LayeredWindowTool(Form LayeredWinForm)
        {
            PngWin = LayeredWinForm;
            // GWL_EXSTYLE =>(-20)，WS_EX_LAYERED => 0x00080000
            SetWindowLongA((int)PngWin.Handle, (int)-20, (int)0x00080000);
        }

        // 根据所接管的窗体中BackgroundImage属性所设置的背景图像生成PNG样式的窗体
        public void SetBitmap()
        {
            if (PngWin.BackgroundImage != null)
                SetBitmap((Bitmap)PngWin.BackgroundImage, 255);
            else
                MessageBox.Show("窗体 BackgroundImage 属性中没有设置可用的背景图像", "错误",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        // 此接口中opacity可以传入0-255表示透明度，这个参数会将整个窗口中的所有元素均设置为相应的透明度，
        // 如果传入的图像中带有Alpha通道，则会在这个基础上再设置为相应的透明度，opacity为255表示按传入
        // 图像的Alpha通道进行处理。
        public void SetBitmap(Bitmap bitmap, byte opacity = 255)
        {
            if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
                throw new ApplicationException("图像必须是32位带Alpha通道的bmp或者PNG图片");

            // 处理过程比较简单
            // 1.创建屏幕的兼容DC
            // 2. 在兼容DC中选入带Alpha通道的32位bmp或PNG图片
            // 3. 调用UpdateLayeredWindow更新以完成分层窗口的绘制

            IntPtr screenDc = LayeredWindowTool.GetDC(IntPtr.Zero);
            IntPtr memDc = LayeredWindowTool.CreateCompatibleDC(screenDc);
            IntPtr hBitmap = IntPtr.Zero;
            IntPtr oldBitmap = IntPtr.Zero;

            try
            {
                // 从bitmap中获取图像对应的位图信息句柄，如果传入的是PNG图片，则内部会转换为32位的位图格式并
                // 提取其中的位图数据信息（包括Alpha通道信息）
                hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
                oldBitmap = LayeredWindowTool.SelectObject(memDc, hBitmap);

                LayeredWindowTool.Size size = new LayeredWindowTool.Size(bitmap.Width, bitmap.Height);
                LayeredWindowTool.Point pointSource = new LayeredWindowTool.Point(0, 0);
                LayeredWindowTool.Point topPos = new LayeredWindowTool.Point(PngWin.Left, PngWin.Top);
                LayeredWindowTool.BLENDFUNCTION blend = new LayeredWindowTool.BLENDFUNCTION();

                blend.BlendOp = LayeredWindowTool.AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = opacity;
                blend.AlphaFormat = LayeredWindowTool.AC_SRC_ALPHA;

                LayeredWindowTool.UpdateLayeredWindow(PngWin.Handle, screenDc, ref topPos, ref size,
                    memDc, ref pointSource, 0, ref blend, LayeredWindowTool.ULW_ALPHA);
            }
            finally
            {
                LayeredWindowTool.ReleaseDC(IntPtr.Zero, screenDc);

                if (hBitmap != IntPtr.Zero)
                {
                    LayeredWindowTool.SelectObject(memDc, oldBitmap);

                    // The documentation says that we have to use the Windows.DeleteObject... but
                    // since there is no such method I use the normal DeleteObject from Win32 GDI
                    // and it's working fine without any resource leak.
                    // Windows.DeleteObject(hBitmap);
                    LayeredWindowTool.DeleteObject(hBitmap);
                }

                LayeredWindowTool.DeleteDC(memDc);
            }

            bitmap.Dispose();
        }


        // =====================================================================================
        // 以下为私有成员

        private enum Bool
        {
            False = 0,
            True = 1
        };

        [StructLayout(LayoutKind.Sequential)]
        private struct Point
        {
            public Int32 x;
            public Int32 y;

            public Point(Int32 x, Int32 y)
            {
                this.x = x; this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct Size
        {
            public Int32 cx;
            public Int32 cy;

            public Size(Int32 cx, Int32 cy)
            {
                this.cx = cx; this.cy = cy;
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private struct ARGB
        {
            public byte Blue;
            public byte Green;
            public byte Red;
            public byte Alpha;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private struct BLENDFUNCTION
        {
            public byte BlendOp;
            public byte BlendFlags;
            public byte SourceConstantAlpha;
            public byte AlphaFormat;
        }

        private Form PngWin; // 所接管的窗口

        private const Int32 ULW_COLORKEY = 0x00000001;
        private const Int32 ULW_ALPHA = 0x00000002;
        private const Int32 ULW_OPAQUE = 0x00000004;

        private const byte AC_SRC_OVER = 0x00;
        private const byte AC_SRC_ALPHA = 0x01;

        // =====================================================================================
        // 以下私有成员为从系统API导入的接口，本质上分层窗口的实现只要导入UpdateLayeredWindow即可，但使用
        // PNG可带有Alpha来绘制分层窗口时只能使用系统的GDI对象，所以用到的其它相关接口也要导入进来，至于为什
        // 么要导入这些接口，以及这些接口的作用可以查阅MSDN中的Win32编程中的知识和系统API信息

        // 更新分层窗口内容
        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
            ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey,
            ref BLENDFUNCTION pblend, Int32 dwFlags);

        // 修改窗口风格
        [DllImport("user32", EntryPoint = "SetWindowLong")]
        public static extern Int32 SetWindowLongA(Int32 hwnd, Int32 nIndex, Int32 dwNewLong);

        // 获取窗口DC
        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr GetDC(IntPtr hWnd);

        // 释放窗口DC
        [DllImport("user32.dll", ExactSpelling = true)]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        // 创建兼容DC
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        // 删除DC
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern Bool DeleteDC(IntPtr hdc);

        // 将GDI对象（注意不是GDI+）选入设备描述表
        [DllImport("gdi32.dll", ExactSpelling = true)]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

        // 删除设备描述表中已选入的GDI对象
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern Bool DeleteObject(IntPtr hObject);

    }
}
